Generating Excel with Watermarks using .NET
TLDR
- Excel does not have a built-in watermark feature; it can be simulated by setting a "full-page background image" or a "header image."
- When generating watermark images, you must adjust the image dimensions based on the Excel
PaperSizesetting, and note that width and height must be swapped for landscape printing. - EPPlus can set the background via
sheet.BackgroundImage.Image, but be aware of .NET 6+ limitations regardingSystem.Drawing.Commonsupport. - NPOI lacks a direct API; you must customize
VmlDrawingvia the underlyingPOIXMLDocumentPartand manually manipulate the XML structure to attach the image. - It is recommended to use
SkiaSharpinstead ofSystem.Drawing.Commonto resolve cross-platform compatibility issues.
Principles of Generating Full-Page Watermark Images
The watermark effect in Excel depends on the view mode:
- Normal and Page Break Preview: Can be displayed by setting a Background Image.
- Page Layout and Printing: Can be displayed by setting a Header Image.
Before generating the image, you must obtain the target paper size (PaperSize). Below are pixel references for common paper sizes (based on default resolution):
| PaperName | Width | Height |
|---|---|---|
| A4 | 827 | 1169 |
| A3 | 1169 | 1654 |
| Letter | 850 | 1100 |
Adjusting Image Dimensions
If the image dimensions do not match the paper, you must perform scaling and centering. If the PaperSize is landscape, be sure to swap the width and height parameters.
public Image ResizeImageBackgroundToFullPage(Image watermark, int width, int height) {
if (watermark.Width > width || watermark.Height > height) {
using (Image image = ZoomOutImage(width, height)) {
return ResizeImageBackgroundToFullPageInternal(width, height, image);
}
}
return ResizeImageBackgroundToFullPageInternal(width, height, watermark);
}Generating Watermarks with EPPlus
When you might encounter this issue: When the development environment uses EPPlus and the project targets .NET Framework or older versions of .NET.
In EPPlus, you can set it directly via the HeaderFooter or BackgroundImage properties:
sheet.HeaderFooter.OddHeader.InsertPicture(watermark, PictureAlignment.Centered);
sheet.BackgroundImage.Image = watermark;WARNING
Versions of EPPlus 6 and later have removed the dependency on System.Drawing.Common. If you upgrade to a newer version, you must use a different graphics library to handle image objects.
Generating Watermarks with NPOI (XLSX)
When you might encounter this issue: When the project cannot use EPPlus or requires lower-level control.
NPOI does not have an API for setting background images directly; you must manually create a VmlDrawing and handle XML relationships.
1. Define the VmlDrawing Class
You must inherit from POIXMLDocumentPart and implement the Commit method to write image information into the VML structure:
private class VmlDrawing : POIXMLDocumentPart {
public string PictureRelId { get; set; }
public Image Image { get; set; }
protected override void Commit() {
PackagePart part = GetPackagePart();
using Stream @out = part.GetOutputStream();
// Convert Pixels to Points
float width = Image.Width * 72 / Image.HorizontalResolution;
float height = Image.Height * 72 / Image.VerticalResolution;
// Write VML XML structure
// Note: You need to fill in the correct XML nodes and attributes based on actual requirements
}
}2. Set Watermark Relationship
Use AddRelation to associate the image with the Sheet, and point legacyDrawingHF to the custom VmlDrawing:
// Add image to Workbook
int pictureIdx = workbook.AddPicture(imageMs.ToArray(), PictureType.PNG);
POIXMLDocumentPart docPart = workbook.GetAllPictures()[pictureIdx] as POIXMLDocumentPart;
// Create VML relationship
VmlDrawing drawing = (VmlDrawing)sheet.CreateRelationship(VmlRelation.Instance, XSSFFactory.GetInstance(), drawingNumber);
drawing.Image = watermark;
drawing.PictureRelId = headerRelPart.Relationship.Id;
// Set header image
sheet.Header.Center = HeaderFooter.PICTURE_FIELD.sequence;
sheet.GetCTWorksheet().legacyDrawingHF = new CT_LegacyDrawing {
id = sheet.GetRelationId(drawing)
};Example Project
For more implementation details and adjustments for the .NET 10 environment (switching to SkiaSharp), please refer to: CloudyWing/ExcelWatermarkSample.
Changelog
- Initial document creation.
- Added link to GitHub example project.